{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "epochs = 10\n",
    "n_test_batches = 200"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 11 - Clasificación con Aprendizaje Profundo y Seguro\n",
    "\n",
    "\n",
    "\n",
    "## Tus datos importan, tu modelo también\n",
    "\n",
    "Los datos son la fuerza detrás del aprendizaje de máquina. Las organizaciones que crean y recolectan datos tienen la capacidad de construir y entrenar sus propios modelos de aprendizaje de máquina. Esto les permite ofrecer a organizaciones externas el uso de tales modelos como un servicio (en inglés, MLaaS). Esto es útil ya que otras organizaciones tal vez no tengan la habilidad de crear estos modelos pero que aún así estarían interesados en usarlos para hacer predicciones de sus propios datos.\n",
    "\n",
    "Sin embargo, un modelo hospedado en la nube aún preserva un problema de privacidad/propiedad intelectual. Para que organizaciones externas lo usen tienen dos opciones: o suben sus datos (por ejemplo, imágenes a ser clasificadas) o descargan el modelo. Subir sus datos puede ser problemático desde el punto de vista de la privacidad, pero descargar el modelo quizá no es una buena opción si la organización que lo creó o es propietario de ese modelo está preocupado por su propiedad intelectual.\n",
    "\n",
    "\n",
    "## Computando con datos encriptados\n",
    "\n",
    "En este contexto, una solución potencial sería encriptar tanto el modelo como los datos de tal manera que una organización pueda usar el modelo de otra organización sin socavar su propiedad intelectual. Varios esquemas de encriptación existen que permiten el cómputo sobre datos encriptado, dentro de los cuales existe la Computación Segura Multiparte ([que se introdujo en detalle aquí en el tutorial 5](https://github.com/OpenMined/PySyft/blob/master/examples/tutorials/translations/espa%C3%B1ol/Parte%2005%20-%20Bienvenido%20a%20la%20caja%20de%20arena.ipynb)) que consiste en una co-propiedad privada. Este esquema depende de protocolos de criptografía tales como SecureNN y SPDZ, cuyos detalles pueden conocerse en [esta excelente entrada de blog](https://mortendahl.github.io/2017/09/19/private-image-analysis-with-mpc/).\n",
    "\n",
    "## Configuración\n",
    "\n",
    "La configuración exacta de este tutorial es la siguiente: considera que estás en un servidor y tienes un conjunto de datos. Primero defines y entrenas un modelo con estos datos de entrenamiento privados. Luego, te pones en contacto con un cliente que tiene parte de sus datos y que quisiera acceder tu modelo para hacer algunas predicciones.\n",
    "\n",
    "Encriptas tu modelo (una red neuronal). El cliente encripta sus datos. Ambos usan sus herramientas encriptadas para usar el modelo y clasificar los datos. Finalmente, el resultado de la predicción regresa al cliente en una manera encriptada para que el servidor (o sea, tú) no pueda saber nada de los datos del cliente (no sabes ni los datos de entrada ni la predicción).\n",
    "\n",
    "Idealmente querríamos compartir de manera aditiva las entradas del `cliente` entre el `servidor` y vice versa con el modelo. Por simplicidad, ambas partes las tendrán dos trabajadores `alice` y `bob`. Si consideras que alice es del cliente y bob del servidor entonces este caso es equivalente al anterior. \n",
    "\n",
    "El cómputo es seguro en el modelo adversarial honesto-pero-seguro (honest-but-curious adversary model), que es estándar en [muchos marcos del CMP](https://arxiv.org/pdf/1801.03239.pdf).\n",
    "\n",
    "\n",
    "**Ahora tenemos todo lo que necesitamos... ¡a comenzar!**\n",
    "\n",
    "\n",
    "Autor:\n",
    "- Théo Ryffel - Twitter: [@theoryffel](https://twitter.com/theoryffel) · GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "\n",
    "Traductores:\n",
    "- Arturo Márquez Flores - Twitter: [@arturomf94](https://twitter.com/arturomf94) \n",
    "- Ricardo Pretelt - Twitter: [@ricardopretelt](https://twitter.com/ricardopretelt)\n",
    "- Carlos Salgado - Github: [@socd06](https://github.com/socd06) \n",
    "- Daniel Firebanks-Quevedo - GitHub: [@thefirebanks](https://www.github.com/thefirebanks)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Imports y especificaciones del modelo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "   También necesitamos ejecutar comandos específicos para importar/comenzar PySyft. Creamos varios trabajadores (llamados `client`, `bob` y `alice`). Por último, definimos el `crypto_provider` que nos brinda todas las primitivas criptográficas que potencialmente necesitemos ([Ve nuestro tutorial sobre CSMP para más detalles](https://github.com/OpenMined/PySyft/blob/master/examples/tutorials/translations/espa%C3%B1ol/Parte%2009%20-%20Intro%20a%20los%20Programas%20Encriptados.ipynb))."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch) \n",
    "client = sy.VirtualWorker(hook, id=\"client\")\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n",
    "crypto_provider = sy.VirtualWorker(hook, id=\"crypto_provider\") "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Definimos la configuración de la tarea de aprendizaje"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Arguments():\n",
    "    def __init__(self):\n",
    "        self.batch_size = 64\n",
    "        self.test_batch_size = 50\n",
    "        self.epochs = epochs\n",
    "        self.lr = 0.001\n",
    "        self.log_interval = 100\n",
    "\n",
    "args = Arguments()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cargar los datos y mandarla a los trabajadores\n",
    "\n",
    "En nuestra configuración asumimos que nuestro servidor tiene acceso a datos para entrenar su modelo. Aquí usamos el conjunto de entrenamiento MNIST."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=True, download=True,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.batch_size, shuffle=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Segundo, el cliente tiene datos y quisiera poder hacer predicciones con ella utilizando el modelo del servidor. Este cliente encripta sus datos compartiendolos aditivamente con los dos trabajadores `alice` y `bob`.\n",
    "> El CSMP usa protocolos criptográficos que requieren trabajo sobre enteros. Utilizamos aquí el la abstracción del tensor de PySyft para convertir tensores PyTorch Float a tensores de precisión fija (Fixed Precision Tensors) usando `.fix_precision()`. Por ejemplo 0.123 con precisión 2 redondea al 2do decimal tal que el número guardado es 12. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=False,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.test_batch_size, shuffle=True)\n",
    "\n",
    "private_test_loader = []\n",
    "for data, target in test_loader:\n",
    "    private_test_loader.append((\n",
    "        data.fix_precision().share(alice, bob, crypto_provider=crypto_provider),\n",
    "        target.fix_precision().share(alice, bob, crypto_provider=crypto_provider)\n",
    "    ))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Especificación de la Red Neuronal Prealimentada \n",
    "Aquí está la especificación de la red utilizada por el servidor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(784, 500)\n",
    "        self.fc2 = nn.Linear(500, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = x.view(-1, 784)\n",
    "        x = self.fc1(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.fc2(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comienza el entrenamiento\n",
    "El entrenamiento es hecho de manera local así que este es meramente un entrenamiento local de PyTorch. Nada especial aquí."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(args, model, train_loader, optimizer, epoch):\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(train_loader):\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        output = F.log_softmax(output, dim=1)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        if batch_idx % args.log_interval == 0:\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * args.batch_size, len(train_loader) * args.batch_size,\n",
    "                100. * batch_idx / len(train_loader), loss.item()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Net()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)\n",
    "\n",
    "for epoch in range(1, args.epochs + 1):\n",
    "    train(args, model, train_loader, optimizer, epoch)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, test_loader):\n",
    "    model.eval()\n",
    "    test_loss = 0\n",
    "    correct = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader:\n",
    "            output = model(data)\n",
    "            output = F.log_softmax(output, dim=1)\n",
    "            test_loss += F.nll_loss(output, target, reduction='sum').item() # suma la pérdida\n",
    "            pred = output.argmax(1, keepdim=True) # obtén el índice del máximo de la probabilidad logarítmica\n",
    "            correct += pred.eq(target.view_as(pred)).sum().item()\n",
    "\n",
    "    test_loss /= len(test_loader.dataset)\n",
    "\n",
    "    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
    "        test_loss, correct, len(test_loader.dataset),\n",
    "        100. * correct / len(test_loader.dataset)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test(args, model, test_loader)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¡Ahora nuestro modelo está entrenado y listo para ser ofrecido como un servicio!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaluación segura"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, como el servidor, mandamos el modelo a los trabajadores que tienen los datos. Porque el modelo es sensible ante la información (¡lo has optimizado!), no tienes que compartir sus pesos, así que compartes el modelo de manera secreta tal como lo hicimos con el conjunto de datos un poco antes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.fix_precision().share(alice, bob, crypto_provider=crypto_provider)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta función de prueba realiza la evaluación encriptada. Los pesos del modelo, los datos de entrada, la predicción y el objetivo usado para la evaluación todos están encriptados.\n",
    "\n",
    "Sin embargo, la sintaxis es muy similar a el entrenamiento de un modelo con PyTorch. Increíble ¿no?\n",
    "\n",
    "La única cosa que podemos desencriptar, del lado del servidor, es la evaluación final para verificar que las predicciones fueron, en promedio, buenas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, test_loader):\n",
    "    model.eval()\n",
    "    n_correct_priv = 0\n",
    "    n_total = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader[:n_test_batches]:\n",
    "            output = model(data)\n",
    "            pred = output.argmax(dim=1) \n",
    "            n_correct_priv += pred.eq(target.view_as(pred)).sum()\n",
    "            n_total += args.test_batch_size\n",
    "# Esta función de prueba realiza la evaluación encriptada. Los pesos del modelo, los datos de entrada, la predicción y el objetivo usado para la evaluación todos están encriptados.\n",
    "\n",
    "# Sin embargo, la sintaxis es muy similar a el entrenamiento de un modelo con PyTorch. Increíble ¿no?\n",
    "\n",
    "# La única cosa que podemos desencriptar, del lado del servidor, es la evaluación final para verificar que las predicciones fueron, en promedio, buenas.\n",
    "            n_correct = n_correct_priv.copy().get().float_precision().long().item()\n",
    "    \n",
    "            print('Test set: Accuracy: {}/{} ({:.0f}%)'.format(\n",
    "                n_correct, n_total,\n",
    "                100. * n_correct / n_total))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test(args, model, private_test_loader)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¡Y voilà! Aquí lo tienes. Has aprendido cómo hacer predicciones seguras de punta a punta: los pesos del modelo del servidor no se han filtrado al cliente y el servidor no tiene información acerca de los datos de entrada o de la salida de la clasificación. \n",
    "\n",
    "En cuanto a rendimiento, clasificar una imagen toma **menos de 0.1 segundos**, aproximadamente **33ms** en mi laptop (2.7 GHz Intel Core i7, 16GB RAM). Sin embargo, esto usa comunicación muy rápida (todos los trabajadores están en mi máquina local). El desempeño dependerá de qué tan rápido se pueden comunicar los trabajadores."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusión\n",
    "\n",
    "Ya viste lo fácil que es usar PyTorch y PySyft para hacer Aprendizaje de Máquina Seguro de manera práctica y proteger los datos del usuario, sin tener que ser un experto en criptografía.\n",
    "\n",
    "Tendremos más sobre este tema pronto, incluyendo capas convolucionales para hacer un benchmark apropiado del desempeño de PySyft con relación a otras librerías, así como entrenamiento encriptado y privado de redes neuronales, lo cual es necesario cuando una organización recurre a datos externos y sensibles para entrenar su propio modelo. ¡Mantente al tanto!\n",
    "\n",
    "¡Felicitaciones por completar esta parte del tutorial! Si te gustó y quieres unirte al movimiento para preservar la privacidad, propiedad descentralizada de IA y la cadena de suministro de IA (datos), puedes hacerlo de las ¡siguientes formas!\n",
    "\n",
    "### Dale una estrella a PySyft en GitHub\n",
    "\n",
    "La forma más fácil de ayudar a nuestra comunidad es por darle estrellas a ¡los repositorios de Github! Esto ayuda a crear consciencia de las interesantes herramientas que estamos construyendo.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### ¡Únete a nuestro Slack!\n",
    "\n",
    "La mejor manera de mantenerte actualizado con los últimos avances es ¡unirte a la comunidad! Tú lo puedes hacer llenando el formulario en [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### ¡Únete a un proyecto de código!\n",
    "\n",
    "La mejor manera de contribuir a nuestra comunidad es convertirte en un ¡contribuidor de código! En cualquier momento puedes ir al _Github Issues_ de PySyft y filtrar por \"Proyectos\". Esto mostrará todos los tiquetes de nivel superior dando un resumen de los proyectos a los que ¡te puedes unir! Si no te quieres unir a un proyecto, pero quieres hacer un poco de código, también puedes mirar más mini-proyectos \"de una persona\" buscando por Github Issues con la etiqueta \"good first issue\".\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Donar\n",
    "\n",
    "Si no tienes tiempo para contribuir a nuestra base de código, pero quieres ofrecer tu ayuda, también puedes aportar a nuestro *Open Collective\"*. Todas las donaciones van a nuestro *web hosting* y otros gastos de nuestra comunidad como ¡hackathons y meetups!\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
